/******************************************************************************
* Copyright (C) Ultraleap, Inc. 2011-2021. *
* *
* Use subject to the terms of the Apache License 2.0 available at *
* http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
* between Ultraleap and you, your company or other organization. *
******************************************************************************/
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Leap.Interaction.Internal.InteractionEngineUtility
{
public static class ColliderUtil
{
//*************
//GENRAL HELPER
///
/// Returns whether or not a given vector is within a given radius to a center point
///
public static bool WithinDistance(Vector3 point, Vector3 center, float radius)
{
return (point - center).sqrMagnitude < radius * radius;
}
public static bool WithinDistance(Vector3 pointToOrigin, float radius)
{
return pointToOrigin.sqrMagnitude < radius * radius;
}
///
/// Given a point and a center, return a new point that is along the axis created by the
/// two vectors, and is a given distance from the center
///
public static Vector3 GetPointAtDistance(Vector3 point, Vector3 center, float distance)
{
return (point - center).normalized * distance + center;
}
///
/// Given a collider and a position relative to the colliders transform, return whether or not the
/// position lies within the collider. Can also optionally extrude the collider.
///
/// Warning: MeshColliders are not fully supported; this will only support testing against a MeshCollider's
/// bounding box, with no extrusion.
///
public static bool IsPointInside(this Collider collider, Vector3 localPosition, float extrude = 0.0f)
{
if (collider is SphereCollider) return (collider as SphereCollider).IsPointInside(localPosition, extrude);
if (collider is BoxCollider) return (collider as BoxCollider).IsPointInside(localPosition, extrude);
if (collider is CapsuleCollider) return (collider as CapsuleCollider).IsPointInside(localPosition, extrude);
if (collider is MeshCollider) return collider.bounds.Contains(collider.transform.TransformPoint(localPosition));
throw nullOrInvalidException(collider);
}
///
/// Given a collider and a position relative to the colliders transform, return the point on the surface
/// of the collider closest to the position. Can also optionally extrude the collider.
///
/// Warning: If you are using a version of Unity pre-5.6, MeshColliders are not fully supported; this will
/// only find the closest point on a MeshCollider's bounding box, with no extrusion.
///
public static Vector3 ClosestPointOnSurface(this Collider collider, Vector3 localPosition, float extrude = 0.0f)
{
if (collider is SphereCollider) return (collider as SphereCollider).ClosestPointOnSurface(localPosition, extrude);
if (collider is BoxCollider) return (collider as BoxCollider).ClosestPointOnSurface(localPosition, extrude);
if (collider is CapsuleCollider) return (collider as CapsuleCollider).ClosestPointOnSurface(localPosition, extrude);
if (collider is MeshCollider) return collider.transform.InverseTransformPoint(collider.ClosestPointOnBounds(collider.transform.TransformPoint(localPosition)));
throw nullOrInvalidException(collider);
}
///
/// Given a list of colliders and a position in global space, return whether or not the position lies
/// within any of the colliders. Can also optionally extrude the collider.
///
public static bool IsPointInside(List colliders, Vector3 globalPosition, float extrude = 0.0f)
{
for (int i = 0; i < colliders.Count; i++)
{
Collider collider = colliders[i];
Vector3 localPosition = collider.transform.InverseTransformPoint(globalPosition);
if (collider.IsPointInside(localPosition, extrude))
{
return true;
}
}
return false;
}
///
/// Given a list of colliders and a position in global space, return the point on the surface of any
/// of the colliders that is closest to the given position. NOTE that this point might be inside of
/// a collider! Can also optionally extrude the colliders.
///
public static Vector3 ClosestPointOnSurfaces(List colliders, Vector3 globalPosition, float extrude = 0.0f)
{
Transform chosenTransform = null;
Vector3 closestPoint = Vector3.zero;
float closestDistance = float.MaxValue;
for (int i = 0; i < colliders.Count; i++)
{
Collider collider = colliders[i];
Vector3 localPoint = collider.transform.InverseTransformPoint(globalPosition);
Vector3 point = collider.ClosestPointOnSurface(localPoint, extrude);
float distance = (globalPosition - point).sqrMagnitude;
if (distance < closestDistance)
{
chosenTransform = collider.transform;
closestDistance = distance;
closestPoint = point;
}
}
return chosenTransform.TransformPoint(closestPoint);
}
//***************
//Raycasting
public static bool SegmentCast(List colliders, Vector3 worldStart, Vector3 worldEnd, out RaycastHit hitInfo)
{
Ray ray = new Ray(worldStart, worldEnd - worldStart);
float dist = Vector3.Distance(worldStart, worldEnd);
hitInfo = new RaycastHit();
bool hitAny = false;
RaycastHit tempHit;
foreach (Collider collider in colliders)
{
if (collider.Raycast(ray, out tempHit, dist))
{
if (!hitAny || tempHit.distance < hitInfo.distance)
{
hitInfo = tempHit;
hitAny = true;
}
}
}
return hitAny;
}
//***************
//SPHERE COLLIDER
public static bool IsPointInside(this SphereCollider collider, Vector3 localPosition, float extrude = 0.0f)
{
localPosition -= collider.center;
return WithinDistance(localPosition, collider.radius + extrude);
}
public static Vector3 ClosestPointOnSurface(this SphereCollider collider, Vector3 localPosition, float extrude = 0.0f)
{
return GetPointAtDistance(localPosition, collider.center, collider.radius + extrude);
}
//************
//BOX COLLIDER
public static bool IsPointInside(this BoxCollider collider, Vector3 localPosition, float extrude = 0.0f)
{
localPosition -= collider.center;
if (Mathf.Abs(localPosition.x) > (collider.size.x / 2.0f) + (extrude / collider.transform.lossyScale.x))
{
return false;
}
if (Mathf.Abs(localPosition.y) > (collider.size.y / 2.0f) + (extrude / collider.transform.lossyScale.y))
{
return false;
}
if (Mathf.Abs(localPosition.z) > (collider.size.z / 2.0f) + (extrude / collider.transform.lossyScale.z))
{
return false;
}
return true;
}
public static Vector3 ClosestPointOnSurface(this BoxCollider collider, Vector3 localPosition, float extrude = 0.0f)
{
localPosition -= collider.center;
Vector3 radius = collider.size / 2.0f;
radius.x += extrude;
radius.y += extrude;
radius.z += extrude;
localPosition.x = Mathf.Clamp(localPosition.x, -radius.x, radius.x);
localPosition.y = Mathf.Clamp(localPosition.y, -radius.y, radius.y);
localPosition.z = Mathf.Clamp(localPosition.z, -radius.z, radius.z);
//If an internal point
if (Mathf.Abs(localPosition.x) < radius.x &&
Mathf.Abs(localPosition.y) < radius.y &&
Mathf.Abs(localPosition.z) < radius.z)
{
//Snap closest axis to the bounds
//Farthest from the center
if (Mathf.Abs(localPosition.x) > Mathf.Abs(localPosition.y))
{
if (Mathf.Abs(localPosition.x) > Mathf.Abs(localPosition.z))
{
//x is farthest
localPosition.x = (localPosition.x > 0) ? radius.x : -radius.x;
}
else
{
//z is farthest
localPosition.z = (localPosition.z > 0) ? radius.z : -radius.z;
}
}
else
{
if (Mathf.Abs(localPosition.y) > Mathf.Abs(localPosition.z))
{
//y is farthest
localPosition.y = (localPosition.y > 0) ? radius.y : -radius.y;
}
else
{
//z is farthest
localPosition.z = (localPosition.z > 0) ? radius.z : -radius.z;
}
}
}
localPosition += collider.center;
return localPosition;
}
//****************
//CAPSULE COLLIDER
///
/// A capsule is defined as a line segment with a radius. This method returns the two endpoints
/// of that segment, as well as it's length, in local space.
///
public static void GetSegmentInfo(this CapsuleCollider collider, out Vector3 localV0, out Vector3 localV1, out float length)
{
Vector3 axis = Vector3.right;
if (collider.direction == 1)
{
axis = Vector3.up;
}
else
{
axis = Vector3.forward;
}
length = Mathf.Max(0, collider.height - collider.radius * 2);
localV0 = axis * length / 2.0f + collider.center;
localV1 = -axis * length / 2.0f + collider.center;
}
public static bool IsPointInside(this CapsuleCollider collider, Vector3 localPosition, float tolerance = 0.0f)
{
Vector3 v0, v1;
float length;
collider.GetSegmentInfo(out v0, out v1, out length);
if (length == 0.0f)
{
return WithinDistance(localPosition, collider.radius + tolerance);
}
float t = Vector3.Dot(localPosition - v0, v1 - v0) / (length * length);
if (t <= 0.0f)
{
return WithinDistance(localPosition, v0, collider.radius + tolerance);
}
if (t >= 1.0f)
{
return WithinDistance(localPosition, v1, collider.radius + tolerance);
}
Vector3 projection = v0 + t * (v1 - v0);
return WithinDistance(localPosition, projection, collider.radius + tolerance);
}
public static Vector3 ClosestPointOnSurface(this CapsuleCollider collider, Vector3 localPosition, float extrude = 0.0f)
{
Vector3 v0, v1;
float length;
collider.GetSegmentInfo(out v0, out v1, out length);
if (length == 0.0f)
{
return GetPointAtDistance(localPosition, collider.center, collider.radius);
}
float t = Vector3.Dot(localPosition - v0, v1 - v0) / (length * length);
if (t <= 0.0f)
{
return GetPointAtDistance(localPosition, v0, collider.radius + extrude);
}
if (t >= 1.0f)
{
return GetPointAtDistance(localPosition, v1, collider.radius + extrude);
}
Vector3 projection = v0 + t * (v1 - v0);
return GetPointAtDistance(localPosition, projection, collider.radius + extrude);
}
public static float DistanceToSegment(Vector3 a, Vector3 b, Vector3 p)
{
float t = Vector3.Dot(p - a, b - a) / Vector3.Dot(a - b, a - b);
if (t <= 0.0f)
{
return Vector3.Distance(p, a);
}
if (t >= 1.0f)
{
return Vector3.Distance(p, b);
}
Vector3 projection = a + t * (b - a);
return Vector3.Distance(p, projection);
}
private static Exception nullOrInvalidException(Collider collider)
{
if (collider == null)
{
return new ArgumentNullException();
}
else
{
return new ArgumentException("Collider type of " + collider.GetType() + " is not supported.");
}
}
}
}